package test;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import message.response.MessageResponse;
import proxy.IProxyCli;
import server.IFileServerCli;
import util.ComponentFactory;
import util.Config;
import util.Util;
import cli.Command;
import cli.Shell;
import cli.TestInputStream;
import cli.TestOutputStream;
import client.IClientCli;

public class LoadTestComponent {
	private int numberOfClients;
	private int uploadsPerMin;
	private int downloadsPerMin;
	private int fileSizeKB;
	private double overwriteRatio;
	
	private ComponentFactory componentFactory;
	private IProxyCli proxy;
	private List<IFileServerCli> fileservers;
	private List<IClientCli> clients;
	private Shell shell;
	private Thread shellThread;
	private Timer uploadTimer;
	private Timer downloadTimer;
	private IClientCli rmiClient;
	private double currentRatio;
	private int newFileCount;
	private int overwriteCount;
	private String downloadFile;
	
	
	public LoadTestComponent(Config config, Shell shell) throws Exception {
		
		this.shell = shell;
		this.shell.register(this);
		shellThread = new Thread(this.shell);
		shellThread.start();
		
		this.numberOfClients = config.getInt("clients");
		this.uploadsPerMin = config.getInt("uploadsPerMin");
		this.downloadsPerMin = config.getInt("downloadsPerMin");
		this.fileSizeKB = config.getInt("fileSizeKB");
		this.overwriteRatio = ((double) (config.getInt("overwriteRatio"))) / 10;
		
		this.fileservers = new ArrayList<IFileServerCli>();
		this.clients = Collections.synchronizedList(new ArrayList<IClientCli>());
		this.componentFactory = new ComponentFactory();
		
		startProxyAndFileservers();
		startClients();
		
		System.out.println("clients started and logged in...");
		
		System.out.println("initializing uploads...");
		initUploads();
		System.out.println("initializing downloads...");
		initDownloads();
	}

	private void startProxyAndFileservers() throws Exception {
		proxy = componentFactory.startProxy(new Config("proxy"), new Shell("proxy", new TestOutputStream(System.out), new TestInputStream()));	
		Thread.sleep(Util.WAIT_FOR_COMPONENT_STARTUP);
		System.out.println("Proxy started!");
		
		proxy.getProxy().generateUserSessionsForTesting(this.numberOfClients);
		System.out.println("User Sessions generated");
		
		for (int i=1; i<=4; i++) {
			this.fileservers.add(componentFactory.startFileServer(new Config("fs"+i), new Shell("fs"+i, new TestOutputStream(System.out), new TestInputStream())));
			Thread.sleep(Util.WAIT_FOR_COMPONENT_STARTUP);
		}
		System.out.println("Servers started!");

	}
	
	private void startClients() throws Exception {
		System.out.println("starting RMI-Client");
		this.rmiClient = componentFactory.startClient(new Config("client"), new Shell("rmiClient", new TestOutputStream(System.out), new TestInputStream()));
		System.out.println("RMI-client: " + rmiClient.login("alice", "12345").toString());
		
		System.out.println("upload a testFile to a server!");
		// upload a testFile to a fileserver (using the rmi-client)
		downloadFile = createNewFile();
		try {
			this.rmiClient.upload(downloadFile);
		} catch (IOException e1) {
			System.out.println("could not upload TestFile to Fileserver");
		}
		System.out.println(rmiClient.subscribe(downloadFile, 100).toString());
		
		for (int i=1; i<=this.numberOfClients; i++) {
			this.clients.add(componentFactory.startClientFromTestComponent(new Shell("client"+i, new TestOutputStream(System.out), new TestInputStream()), "files/client", "localhost", 11900));
			//Thread.sleep(Util.WAIT_FOR_COMPONENT_STARTUP);
			System.out.println("Client " + i  + " started");
			
		}
		// login clients
		
		System.out.println("Clients started!");
		int client_id = 1;
		for (IClientCli client : this.clients) {
			//System.out.println(client.login("client"+client_id, "defaultpassword"+client_id).toString());
			client.login("client"+client_id, "12345");
			client_id++;
		}
		
	}

	
	private void initUploads() {
		this.uploadTimer = new Timer();
		this.overwriteCount = 0;
		this.newFileCount = 0;
		this.currentRatio = 1.0;
		final String overwriteFile = createNewFile();
		
		TimerTask uploadTask = new TimerTask() {
			@Override
			public void run() {
				for (IClientCli client : clients) {
				
					try {
						// overwriteRatio:
						
						if (currentRatio > overwriteRatio) {
							//create new temp file
							//System.out.println(client.upload(createNewFile()));
							client.upload(createNewFile());
							newFileCount++;
						} else {
							//System.out.println(client.upload(overwriteFile));
							client.upload(overwriteFile);
							overwriteCount++;
						}
						
						int sum = newFileCount + overwriteCount;					
						currentRatio = (double)overwriteCount/sum;
						//System.out.println("currentRatio: " + currentRatio);
		
						
					} catch (IOException e) {
						System.out.println("upload Error");
					}
				}
				
			}
		};
		int period = (int) ((double) 60/this.uploadsPerMin * 1000);
		uploadTimer.schedule(uploadTask, new Date(), period);
	}
	
	private void initDownloads() {		
		this.downloadTimer = new Timer();
		TimerTask downloadTask = new TimerTask() {
			@Override
			public void run() {
				for (IClientCli client : clients) {
					try {
						client.download(downloadFile);
					} catch (IOException e) {
						System.out.println("download Error");
					}
				}
				
			}
		};
		int period = (int) ((double) 60/this.downloadsPerMin * 1000);
		downloadTimer.schedule(downloadTask, new Date(), period);
		
	}
	
	/**
	 * creates a temporary file with a specific fileSize
	 * http://kodejava.org/how-do-i-create-a-temporary-file/
	 */
	private String createNewFile() {
		File data = null;
		try {
			data = File.createTempFile("newFile", ".txt", new File("files/client"));
	        data.deleteOnExit();
	        
	        //
	        // Write data into temporary file
	        //
	        BufferedWriter writer = new BufferedWriter(new FileWriter(data));
	        String randomString="";
	        
	        StringBuffer outputBuffer = new StringBuffer(this.fileSizeKB*1000);
	        for (int i = 0; i < this.fileSizeKB*1000; i++){
	           outputBuffer.append("#");
	        }
	        randomString = outputBuffer.toString();

        try {
            writer.write(randomString);
        } finally {
            writer.flush();
            writer.close();
        }
        
		}catch (IOException e) {
			e.printStackTrace();
		}
		return data.getName();
	}
	
	public static void main(String[] args) {
		
		try {
			Shell shell = new Shell("LoadTestComponent", System.out, System.in);
			new LoadTestComponent(new Config("loadtest"), shell);

		} catch (Exception e) {
			System.out.println("Test Error");
			e.printStackTrace();
		}
	}
	
	@Command
	public MessageResponse exit() throws IOException {
		exitMethod();
		return new MessageResponse("Test terminated");
	}
	
	/**
	 * Delete all created testFiles on Fileserver directory
	 * @param id fileserver_id
	 */
	private void cleanupFileserver(int id) {
		final File folder = new File("files/fileserver"+id);
		final File[] files = folder.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(final File dir, final String name) {
				return name.matches("newFile.*\\.txt");
			}
		});
		for (final File file : files) {
			if (!file.delete()) {
				System.err.println("Can't remove " + file.getAbsolutePath());
			}
		}
	}
	
	private void exitMethod() throws IOException {
		System.out.println("exiting...");
		this.uploadTimer.cancel();
		this.downloadTimer.cancel();

		try {
			this.proxy.exit();
		} catch (IOException e) {
			System.out.println("Proxy could not be stopped");
		}
		
		int fileserverCount = 0;
		for (IFileServerCli server : this.fileservers) {
			try {
				// exit and cleanup fileserver
				server.exit();
				fileserverCount++;
				cleanupFileserver(fileserverCount);
			} catch (IOException e) {
				System.out.println("Server could not be stopped");
			}
		}
		this.fileservers.clear();
		
		this.rmiClient.exit();
		
		int client_count=1;
		for (IClientCli client : this.clients) {
			try {
				client.exit();
				System.out.println("Client " + client_count + " stopped");
				client_count++;
			} catch (IOException e) {
				System.out.println("Client " + client_count + " could not be stopped");
			}
		}
		this.clients.clear();
		
		shellThread.interrupt();
		shell.close();
		System.in.close();
	
	}
	

}
